package com.hghivln.blog.common.aop;

import com.alibaba.fastjson.JSON;
import com.hghivln.blog.common.annotation.IgnoreLog;
import com.hghivln.blog.common.context.ThreadContext;
import com.hghivln.blog.common.lang.StateEnums;
import com.hghivln.blog.entity.Log;
import com.hghivln.blog.service.LogService;
import com.hghivln.blog.utils.StringUtils;
import com.hghivln.blog.utils.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 日志记录AOP
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Autowired
    private LogService logService;

    /**
     * 声明切点
     */
    @Pointcut("execution( * com.hghivln.blog.controller..*(..))")
    public void logPointCut() {
    }

    /**
     * 环绕通知
     */
    @Around("logPointCut()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {

        HttpServletRequest request = WebUtils.getRequest();

        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method signatureMethod = signature.getMethod();
        boolean ignoreLog = signatureMethod.isAnnotationPresent(IgnoreLog.class);

        if (!ignoreLog) {
            // 获取 请求的参数
            Object[] args = pjp.getArgs();
            boolean logParaFlag = ObjectUtils.isNotEmpty(args) &&
                    !(args[0] instanceof ServletRequest) &&
                    !(args[0] instanceof MultipartFile);
            // 获取线程上下文
            Log logger = ThreadContext.get().getLog();
            if (logger == null) {
                logger = new Log();
                ThreadContext.get().setLog(logger);
            }
            if (logParaFlag) {
                // 将参数所在的数组转换成json
                String params = JSON.toJSONString(args[0]);
                log.info("请求参数：{}", params);
                logger.setLogParams(params);
            }

            // 获取 请求方式和请求IP
            String uri = request.getRequestURI();
            String method = request.getMethod();
            String ip = StringUtils.getRemoteIp(request);
            log.info("请求地址：{}", uri);
            log.info("请求方式：{}", method);
            log.info("请求IP：{}", ip);
            // 获取请求的 controller   从切面织入点处通过反射机制获取织入点处的方法
            String controller = signature.getDeclaringTypeName();
            log.info("请求方法：{}.{}", controller, signature.getName());

            // 记录日志
            logger.setLogUrl(uri);
            logger.setLogStatus(StateEnums.REQUEST_SUCCESS.getCode());
            logger.setLogMethod(method);
            logger.setLogIp(ip);
            logger.setLogUa(request.getHeader("user-Agent"));
            logger.setLogController(controller);
        }

        // 记录方法执行时间
        long start = System.currentTimeMillis();
        Object ob = pjp.proceed();
        long end = System.currentTimeMillis();
        long time = end - start;
        log.info("方法执行耗时:{}", time);
        if (!ignoreLog) {
            Log logger = ThreadContext.get().getLog();
            logger.setLogTime(time);
            // 记录返回值
            String result = JSON.toJSONString(ob);
            log.info("返回值：{}", result);
            logger.setLogResult(result);
            // 记录日志
            logService.saveLog(logger);
            ThreadContext.get().remove();
        }
        return ob;
    }

    /**
     * 异常通知
     */
    @AfterThrowing(pointcut = "logPointCut()", throwing = "throwable")
    public void doException(JoinPoint joinPoint, Throwable throwable) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        boolean ignoreLog = method.isAnnotationPresent(IgnoreLog.class);
        if (!ignoreLog) {
            Log logger = ThreadContext.get().getLog();
            logger.setLogStatus(StateEnums.REQUEST_ERROR.getCode());
            logger.setLogMessage(throwable.getMessage());
            logger.setLogTime(-1L);
            logService.saveLog(logger);
            ThreadContext.get().remove();
        }
    }
}
